package org.springblade.modules.auth.endpoint;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springblade.common.cache.DictCache;
import org.springblade.common.constant.CommonConstant;
import org.springblade.common.utils.SqlInjectionUtils;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.modules.auth.enums.UserEnum;
import org.springblade.modules.auth.granter.PhoneVerificationTokenGranter;
import org.springblade.modules.auth.provider.ITokenGranter;
import org.springblade.modules.auth.provider.TokenGranterBuilder;
import org.springblade.modules.auth.provider.TokenParameter;
import org.springblade.modules.auth.utils.TokenUtil;
import org.springblade.modules.auth.utils.WxMaConfiguration;
import org.springblade.modules.system.entity.Dict;
import org.springblade.modules.system.entity.UserInfo;
import org.springblade.modules.system.entity.UserMiniappWeixin;
import org.springblade.modules.system.service.IUserMiniappWeixinService;
import org.springblade.modules.system.service.IUserService;
import org.springblade.modules.system.vo.TenantInfoVO;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description 微信小程序用户令牌
 * @Author ysq
 * @Date 2021/12/16
 **/
@NonDS
@ApiSort(2)
@RestController
@AllArgsConstructor
@RequestMapping("/repair-auth")
@Api(value = "微信授权认证", tags = "授权接口")
public class WxUserTokenEndpoint {

	private final IUserService userService;
	private final BladeRedis bladeRedis;
	private final IUserMiniappWeixinService userMiniappWeixinService;

	@ApiLog("微信登录请求")
	@GetMapping("/login/request/{appid}")
	@ApiOperation(value = "微信登录请求", notes = "传入appid,code")
	public R<WxMaJscode2SessionResult> login(@PathVariable String appid, String code) {
		if (Func.isBlank(code)) {
			throw new ServiceException("empty code");
		}

		if (SqlInjectionUtils.checkV2(code)) {
			throw new ServiceException("非法传参");
		}

		final WxMaService wxService = WxMaConfiguration.getMaService(appid);

		try {
			WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(code);
			this.storageWxSession(session, appid);
			session.setSessionKey(null);
			return R.data(session);
		} catch (WxErrorException e) {
			throw new ServiceException(e.getMessage());
		}
	}

	@ApiLog("微信登录回调")
	@PostMapping("/login/callback/{appid}")
	@ApiOperation(value = "微信登录回调")
	public R loginCallback(@PathVariable String appid, String openid, String encryptedData, String iv) {
		final WxMaService wxService = WxMaConfiguration.getMaService(appid);
		//获取sessionKey
		List sessionKeys = bladeRedis.hmGet(appid + "_" + openid,"sessionKey");
		if (Func.isEmpty(sessionKeys)) {
			throw new ServiceException("sessionKey is empty！");
		}
		String sessionKey = sessionKeys.get(0).toString();
		if (Func.isEmpty(sessionKey)) {
			throw new ServiceException("sessionKey is empty！");
		}

		// 解密
		WxMaPhoneNumberInfo phoneNoInfo = wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);

		String ua = WebUtil.getRequest().getHeader(WebUtil.USER_AGENT_HEADER);
		if(ua.contains("MicroMessenger")){
			List<Dict> dictList = DictCache.getList("phone_limit_login");
			if(Func.isNotEmpty(dictList)){
				long count = dictList.stream().filter(e -> e.getDictKey().equals(phoneNoInfo.getPhoneNumber())).count();
				if(count > 0){
					throw new ServiceException("小程序账号已冻结，请登录APP使用");
				}
			}
		}

		List<TenantInfoVO> tenantInfos = userService.getTenantInfoByPhone(phoneNoInfo.getPhoneNumber());
		Map<String, Object> map = new HashMap<>(2);
		map.put("phoneNoInfo", phoneNoInfo);
		map.put("tenantInfos", tenantInfos);
		return R.data(map);
	}

	@ApiLog("获取微信授权认证令牌")
	@PostMapping("/oauth/wx/token/{appid}")
	@ApiOperation(value = "获取微信授权认证令牌")
	public R<Kv> token(@PathVariable String appid, @RequestParam(required = false) String openid, String tenantId, String username, String password,
					   @ApiIgnore @RequestHeader(name = TokenUtil.DEPT_HEADER_KEY, required = false) String deptId,
					   @ApiIgnore @RequestHeader(name = TokenUtil.ROLE_HEADER_KEY, required = false) String roleId) {
		String cache = bladeRedis.get(CommonConstant.LOGIN_KEY + username);
		if (StringUtil.isBlank(password) || !StringUtil.equalsIgnoreCase(cache, password)) {
			throw new ServiceException(TokenUtil.LOGIN_ILLEGAL);
		} else {
			bladeRedis.del(CommonConstant.LOGIN_KEY + username);
		}

		String grantType = PhoneVerificationTokenGranter.GRANT_TYPE;
		String userType = UserEnum.APP.getName();
		TokenParameter tokenParameter = new TokenParameter();
		tokenParameter.getArgs().set("tenantId", tenantId)
			.set("username", username)
			.set("password", password)
			.set("grantType", grantType)
			.set("userType", userType)
			.set("deptId", deptId)
			.set("roleId", roleId);

		ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
		UserInfo userInfo = granter.grant(tokenParameter);

		String ua = WebUtil.getRequest().getHeader(WebUtil.USER_AGENT_HEADER);
		if(ua.contains("MicroMessenger")){
			List<Dict> dictList = DictCache.getList("phone_limit_login");
			if(Func.isNotEmpty(dictList)){
				long count = dictList.stream().filter(e -> e.getDictKey().equals(userInfo.getUser().getPhone())).count();
				if(count > 0){
					throw new ServiceException("小程序账号已冻结，请登录APP使用");
				}
			}
		}

		//代表离职
		int userStatus = 2;
		if (userInfo.getStatus() == userStatus) {
			return R.fail(HttpServletResponse.SC_BAD_REQUEST, "用户已经离职");
		}
		if (userInfo == null) {
			return R.fail(HttpServletResponse.SC_BAD_REQUEST, "用户不存在");
		}
		if (Func.isNoneBlank(openid)) {
			this.bindWxUser(appid, openid, userInfo.getUser().getId());
		}
		return R.data(TokenUtil.createAuthInfo(userInfo));
	}

	@ApiLog("手机验证码登录回调")
	@PostMapping("/phone/login/callback/{appid}")
	@ApiOperation(value = "手机验证码登录回调")
	public R<WxMaJscode2SessionResult> phoneLoginCallback(@PathVariable String appid, String code, Long userId) {
		if (Func.isBlank(code) || Func.isEmpty(userId)) {
			throw new ServiceException("parameter error!");
		}

		final WxMaService wxService = WxMaConfiguration.getMaService(appid);

		try {
			WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(code);
			this.storageWxSession(session, appid);
			session.setSessionKey(null);
			// 建立微信用户与用户关系
			this.bindWxUser(appid, session.getOpenid(), userId);
			return R.data(session);
		} catch (WxErrorException e) {
			throw new ServiceException(e.getMessage());
		}
	}

	/**
	 * 存储session到redis
	 * @param session
	 * @param appid
	 */
	private void storageWxSession(WxMaJscode2SessionResult session, String appid) {
		Map<Object, Object> map =new HashMap<>(4);
		map.put("sessionKey", session.getSessionKey());
		map.put("openid", session.getOpenid());
		map.put("unionid", session.getUnionid());
		// appid + openid作为key存入redis
		bladeRedis.hMset(appid + "_" + session.getOpenid(), map);
	}

	/**
	 * 建立用户与微信用户关联关系
	 * @param appid
	 * @param openid
	 * @param userId
	 */
	private void bindWxUser(String appid, String openid, Long userId) {
		UserMiniappWeixin userWeixin = userMiniappWeixinService.getOne(Wrappers.<UserMiniappWeixin>lambdaQuery().eq(UserMiniappWeixin :: getUserId, userId).eq(UserMiniappWeixin :: getIsDeleted, 0),false);
		// 获取unionid
		String unionidStr = String.valueOf(bladeRedis.hmGet(appid + "_" + openid,"unionid").get(0));
		String unionid = unionidStr == "null" ? null : unionidStr;
		if (Func.isEmpty(userWeixin)) {
			userWeixin = new UserMiniappWeixin();
			userWeixin.setUserId(userId);
			userWeixin.setOpenid(openid);
			userWeixin.setUnionid(unionid);
			userMiniappWeixinService.save(userWeixin);
		} else if (!Func.equalsSafe(openid, userWeixin.getOpenid())){
			userWeixin.setOpenid(openid);
			userWeixin.setUnionid(unionid);
			userMiniappWeixinService.updateById(userWeixin);
		} else if (Func.equalsSafe(openid, userWeixin.getOpenid()) && !Func.equalsSafe(unionid, userWeixin.getUnionid())) {
			userWeixin.setUnionid(unionid);
			userMiniappWeixinService.updateById(userWeixin);
		}
	}
}
